# FluxValidator - AJAX Form Validation for Rails
# Copyright (c) 2007 Peter Gumeson
# 
# FluxValidator is freely distributable under the terms of an MIT license

#------------------------------------

class String
  # Return a reference to the class or module matching the String
  def to_class
    self.split('::').inject(Object) { |s,k| Kernel.const_get(k) } rescue nil
  end
end

#------------------------------------

# validations.rb
module ActiveRecord::Validations::ClassMethods
  # Overrides the validation rule that validates a password or email address
  # field with a confirmation. Except this version adds the error message
  # to the confirmation field instead of the defined field.
  def validates_confirmation_of(*attr_names)
    configuration = { :message => ActiveRecord::Errors.default_error_messages[:confirmation], :on => :save }
    configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)

    attr_accessor *(attr_names.map { |n| "#{n}_confirmation" })

    validates_each(attr_names, configuration) do |record, attr_name, value|
      record.errors.add("#{attr_name}_confirmation", "doesn't match #{attr_name}") unless record.send("#{attr_name}_confirmation").nil? or value == record.send("#{attr_name}_confirmation")
    end
  end
end

#------------------------------------

# active_record_helper.rb
module ActionView
  class Base
    @@field_error_proc = Proc.new{ |html_tag, instance| "<span id=\"#{instance.wrapper_id}\" class=\"fieldWithErrors\">#{html_tag}</span>" }
    cattr_accessor :field_error_proc
    
    @@field_blank_error_proc = Proc.new{ |html_tag, instance| "<span id=\"#{instance.wrapper_id}\">#{html_tag}</span>" }
    cattr_accessor :field_blank_error_proc
  end
  
  module Helpers
    class InstanceTag
      
      attr_reader :wrapper_id
      
      def tag(name, options)
        @wrapper_id = options["id"]+'_field' if options["id"]
        wrap_element = (options["id"].nil? || name.to_s == 'div' || name.to_s == 'span') ? false : true
        if wrap_element && object.respond_to?("errors") && object.errors.respond_to?("on")
          error_wrapping(tag_without_error_wrapping(name, options), object.errors.on(@method_name))
        elsif wrap_element
          blank_error_wrapping(tag_without_error_wrapping(name, options))
        else
          tag_without_error_wrapping(name, options)
        end
      end
      
      def content_tag(name, value, options)
        @wrapper_id = options["id"]+'_field' if options["id"]
        wrap_element = (options["id"].nil? || name.to_s == 'div' || name.to_s == 'span') ? false : true
        if wrap_element && object.respond_to?("errors") && object.errors.respond_to?("on")
          error_wrapping(content_tag_without_error_wrapping(name, value, options), object.errors.on(@method_name))
        elsif wrap_element
          blank_error_wrapping(content_tag_without_error_wrapping(name, value, options))
        else
          content_tag_without_error_wrapping(name, value, options)
        end
      end
      
      def to_date_select_tag(options = {})
        if object.respond_to?("errors") && object.errors.respond_to?("on")
          error_wrapping(to_date_select_tag_without_error_wrapping(options), object.errors.on(@method_name))
        else
          blank_error_wrapping(to_date_select_tag_without_error_wrapping(options))
        end
      end
      
      def to_datetime_select_tag(options = {})
        if object.respond_to?("errors") && object.errors.respond_to?("on")
            error_wrapping(to_datetime_select_tag_without_error_wrapping(options), object.errors.on(@method_name))
          else
            blank_error_wrapping(to_datetime_select_tag_without_error_wrapping(options))
        end
      end
    
      def to_time_select_tag(options = {})
        if object.respond_to?("errors") && object.errors.respond_to?("on")
          error_wrapping(to_time_select_tag_without_error_wrapping(options), object.errors.on(@method_name))
        else
          blank_error_wrapping(to_time_select_tag_without_error_wrapping(options))
        end
      end
    
      def error_wrapping(html_tag, has_error)
        has_error ? Base.field_error_proc.call(html_tag, self) : Base.field_blank_error_proc.call(html_tag, self)
      end
    
      def blank_error_wrapping(html_tag)
        Base.field_blank_error_proc.call(html_tag, self)
      end
    end
  end
end

#------------------------------------

# form_helper.rb
module ActionView::Helpers
  module FormHelper
    # Returns a string containing the error message attached to the +method+ on the +object+, if one exists.
    # This error message is wrapped in a SPAN tag, which can be specialized to include both a +prepend_text+ and +append_text+
    # to properly introduce the error. Examples (post has an error message "can't be empty" on the title attribute):
    #
    #   <%= error_message_on "post", "title" %> =>
    #     <div class="formErrors">can't be empty</span>
    #
    #   <%= error_message_on "post", "title", {:class => "inputError"}, "Title simply ", " (or it won't work)" %> =>
    #     <span class="inputError">Title simply can't be empty (or it won't work)</span>
    def error_message_on(object_name, method, options = {}, prepend_text = "", append_text = "")
      if options.instance_of?(String)
        prepend_text = options
        append_text = prepend_text
        options = {}
        options[:css_class] = append_text
      end
      InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_error_message_on_tag(options, prepend_text, append_text)
    end
    
    # Returns a list containing the error messages attached to the +method+ on the +object+, if one exists.
    # Each error message is wrapped in a LI tag, which can be specialized to include both a +prepend_text+ and +append_text+
    # to properly introduce the error.
    #   <%= error_messages_on "post", "title" %> =>
    #     <span class="formErrors"><ul><li>can't be empty</li><li>must have at least 2 characters</li></ul></span>
    def error_messages_on(object_name, method, options = {}, prepend_text = "", append_text = "")
      InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_error_messages_on_tag(options, prepend_text, append_text)
    end
    
    # Returns the inner html for error_messages_on
    def inner_error_messages_on(object_name, method, prepend_text = '', append_text = '')
      prepend_text = "#{method.to_s.humanize.downcase} " if method
      if (obj = instance_variable_get("@#{object_name}")) && (errors = obj.errors.on(method))
        errors = [errors] unless errors.is_a?(Array)
        error_messages = errors.map {|error| content_tag(:li, "#{prepend_text}#{error}#{append_text}") }
        content_tag(:ul, error_messages)
      else
        ''
      end
    end
    
    def instance_variable(name)
      instance_variable_get("@#{name}")
    end
  end
  
  class InstanceTag #:nodoc:
    def to_error_message_on_tag(options = {}, prepend_text = "", append_text = "")
      options = options.stringify_keys
      add_error_id(options)
      options["class"] ||= "formError"
      prepend_text = "#{@method_name.to_s.humanize.downcase} " if @method_name
      if (obj = @template_object.instance_variable(@object_name)) && (errors = obj.errors.on(@method_name))
        content_tag("span", "#{prepend_text}#{errors.is_a?(Array) ? errors.first : errors}#{append_text}", options)
      else
        ''
      end
    end
    
    def to_error_messages_on_tag(options = {}, prepend_text = "", append_text = "")
      options = options.stringify_keys
      add_errors_id(options)
      options["class"] ||= "formErrors"
      if (obj = @template_object.instance_variable(@object_name)) && (errors = obj.errors.on(@method_name))
        inner = @template_object.inner_error_messages_on(@object_name, @method_name, prepend_text, append_text)
      else
        options.delete("class")
        inner = ''
      end
      content_tag(:span, inner, options)
    end
  
    private

    def add_error_id(options)
      if options.has_key?("index")
        options["id"]   ||= "#{@object_name}_#{options["index"]}_#{@method_name}_error"
        options.delete("index")
      elsif defined?(@auto_index)
        options["id"]   ||= "#{@object_name}_#{@auto_index}_#{@method_name}_error"
      else
        options["id"]   ||= "#{@object_name}_#{@method_name}_error"
      end
    end

    def add_errors_id(options)
      if options.has_key?("index")
        options["id"]   ||= "#{@object_name}_#{options["index"]}_#{@method_name}_errors"
        options.delete("index")
      elsif defined?(@auto_index)
        options["id"]   ||= "#{@object_name}_#{@auto_index}_#{@method_name}_errors"
      else
        options["id"]   ||= "#{@object_name}_#{@method_name}_errors"
      end
    end
  end

  class FormBuilder #:nodoc:
    self.field_helpers << "error_message_on"
    self.field_helpers << "error_messages_on"
    self.field_helpers << "inner_error_messages_on"
    
    def error_message_on(method, options = {:class => "formError"}, prepend_text = "", append_text = "")
      if options.instance_of?(String)
        prepend_text = options
        append_text = prepend_text
        options = {}
        options[:css_class] = append_text
      end
      @template.error_message_on(@object_name, method, options.merge(:object => @object), prepend_text, append_text)
    end
    
    def error_messages_on(method, options = {:class => "formErrors"}, prepend_text = "", append_text = "")
      @template.error_messages_on(@object_name, method, options.merge(:object => @object), prepend_text, append_text)
    end      
  end
end

#------------------------------------

# form__tag_helper.rb
module ActionView::Helpers::FormTagHelper
  def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &block)
    html_options = options.stringify_keys
    html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart")
    html_options["action"]  = url_for(url_for_options, *parameters_for_url)

    method_tag = ""
    
    case method = html_options.delete("method").to_s
      when /^get$/i # must be case-insentive, but can't use downcase as might be nil
        html_options["method"] = "get"
        options.merge!({:method => "get".to_sym})
      when /^post$/i, "", nil
        html_options["method"] = "post"
        options.merge!({:method => "post".to_sym})
      else
        html_options["method"] = "post"
        method_tag = content_tag(:span, tag(:input, :type => "hidden", :name => "_method", :value => method), :style => 'margin:0;padding:0')
        options.merge!({:method => method.to_sym})
    end
    
    validation_tag = ""
    
    result = ActionController::Routing::Routes.recognize_path(html_options["action"], options)
    validation_path = url_for(:controller => result[:controller], :action => "validate_#{result[:action]}_form")
    validation_tag = content_tag(:div, tag(:input, :type => "hidden", :name => "_validation", :value => validation_path), :style => 'margin:0;padding:0')
    
    if block_given?
      content = capture(&block)
      concat(tag(:form, html_options, true) + method_tag + validation_tag, block.binding)
      concat(content, block.binding)
      concat("</form>", block.binding)
    else
      tag(:form, html_options, true) + method_tag + validation_tag
    end
  end
end

#------------------------------------

# date_helper.rb
class ActionView::Helpers::InstanceTag
  def options_with_prefix(position, options)
    prefix = "#{@object_name}"
    if options[:index]
      prefix << "[#{options[:index]}]"
    elsif @auto_index
      prefix << "[#{@auto_index}]"
    end
    @wrapper_id = "#{prefix}_#{@method_name}_field"
    options.merge(:prefix => "#{prefix}[#{@method_name}(#{position}i)]")
  end
end
